/*
SteelNzb
Copyright (C) 2008  Wessel v. Staal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/* Wessel van Staal */
/* NNTP protocol handler */

#include "socket.h"
#include "nntp.h"
#include "global.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#if defined(WIN32) || defined(WINCE)
#include <winsock2.h>
#include <windows.h>
#define stricmp _stricmp
#define unlink(x) DeleteFileA(x)
#ifndef WINCE
#include <direct.h>
#include <crtdbg.h>
#endif
#else
#include <unistd.h>
#define stricmp strcasecmp
#endif

typedef struct nntpState
{
	unsigned int socket;
	void* userData;
} nntpState;

int nntp_getStatusCode(char* data);
int nntp_endCmp(char* buffer, int length);

/**
	Connect to NNTP server
	@param host Host address, ip or dns
	@param port The port number
	@returns pointer to nntp state
*/
void* nntp_connect(const char* host, int port)
{
	int socket = 0;
	nntpState* state = NULL;
	int status = 0;
	int size = 0;
	void* buffer = socket_createBuffer();
	char* data;
	
	if ( (socket = socket_connect(host, port)) < 1 )
	{
		socket_freeBuffer(buffer);
		return NULL;
	}

	if ( (state= malloc(sizeof(nntpState))) == NULL )
	{
		perror("nntp_Connect");
		exit(1);
	}

	state->socket = socket;

	/* receive the first line from server indicating we are welcome or not */
	if ( (data = socket_receiveLine(state->socket, buffer, &size, 1)) == NULL )
	{
		socket_freeBuffer(buffer);
		nntp_close(state);
		return NULL;
	}

	status = nntp_getStatusCode(data);

	if ( status != NNTP_SERVER_READY_POST && status != NNTP_SERVER_READY_NOPOST )
	{
		socket_freeBuffer(buffer);
		nntp_close(state);
		return NULL;
	}

	socket_freeBuffer(buffer);

	return state;
}

/**
	Authenticates the nttp session
	@param nntpstate The nntp session to authenticate
	@param username The username in plain text
	@param password The password in plain text
	@returns int indicating result
*/
int nntp_login(void* nntpstate, const char* username, const char* password)
{
	char* data = NULL;
	char send_buffer[5000];
	nntpState* state= nntpstate;
	int size = 0;
	void* buffer = socket_createBuffer();
	int ret = NNTP_ERROR;

	if ( username != NULL )
	{
		sprintf(send_buffer, "AUTHINFO USER %s\r\n", username);

		if ( socket_send(state->socket, send_buffer, (int)strlen(send_buffer)) )
		{
			if ( (data = socket_receiveLine(state->socket, buffer, &size, 1)) != NULL )
			{
				if ( nntp_getStatusCode(data) == NNTP_MORE_AUTH )
				{
					sprintf(send_buffer, "AUTHINFO PASS %s\r\n", password);

					if ( socket_send(state->socket, send_buffer, (int)strlen(send_buffer)) )
					{
						if ( (data = socket_receiveLine(state->socket, buffer, &size, 1)) != NULL )
						{
							if ( nntp_getStatusCode(data) != NNTP_AUTH_OK &&
								nntp_getStatusCode(data) != NNTP_AUTH_OK2 )
								ret = NNTP_ERROR_LOGIN;
							else
								ret = NNTP_OK;
						}
					}
					
				}
				else if ( nntp_getStatusCode(data) == NNTP_AUTH_OK ||
							nntp_getStatusCode(data) == NNTP_AUTH_OK2 )
				{
					return NNTP_OK;
				}
			}
		}
	}

	socket_freeBuffer(buffer);

	return ret;
}

int nntp_getStatusCode(char* data)
{
	if ( data == NULL )
		return 0;
	else
		return atoi(data);
}

/**
	Downloads an artice to a file. 
	@param nntpstate The NNTP session
	@param msg_id The article identifier
	@param filepath The target path for the file (including filename)
	@param progressCallback Function that will be invoked when data is downloaded
	@returns int indicating success or not
*/
int nntp_downloadArticleToFile(void* nntpstate, const char* msg_id, const char* filepath, void(*progressCallback)(void* userdata, int downloadedSize))
{
	char send_buffer[2048];
	int pos = 0;
	FILE* fp = NULL;
	int stop = 0;
	int i = 0;
	char filebuffer[1024];
	char* end = "\n.\r\n";
	nntpState* state= nntpstate;
	void* buffer = socket_createBuffer();
	char* data = NULL;
	int result = 0;
	int length = sizeof(filebuffer);

	fp = fopen(filepath, "wb");

	if ( fp == NULL )
	{
		socket_freeBuffer(buffer);
		return 0;
	}

	/* we are only interested in the body of the article */
	sprintf(send_buffer, "BODY <%s>\r\n", msg_id);

	if ( !socket_send(state->socket, send_buffer, (int)strlen(send_buffer)) )
	{
		fclose(fp);
		socket_freeBuffer(buffer);
		return NNTP_ERROR;
	}

	if ( (data = socket_receiveLine(state->socket, buffer, &length, 1)) == NULL )
	{
		fclose(fp);
		socket_freeBuffer(buffer);
		return NNTP_ERROR;
	}

	if ( nntp_getStatusCode(data) != NNTP_BODY_FOLLOWS )
	{
		fclose(fp);
		socket_freeBuffer(buffer);
		return NNTP_ERROR_ARTICLE;
	}

	/* get the remaining data after the newline */
	data = socket_emptyBuffer(buffer, &length);
	
	/* copy the remaining data to the filebuffer we use in the download loop */
	memcpy(filebuffer, data, length);

	do
	{
		result = 0;

		progressCallback(state->userData, length);

		/* 
			it is possible we received part of a sequence during last iteration, and the rest followed now.
			we still need to apply unescaping and check whether the sequence matches the end sequence
		*/

		if ( pos > 0 )
		{
			if ( (pos == 2 && filebuffer[0] == '.') || (pos == 1 && filebuffer[0] == '.' && filebuffer[1] == '.') )
			{
				memmove(filebuffer, filebuffer+1, length-1);
				length--;
			}
			else
			{
				if ( memcmp(filebuffer, end+pos, 4-pos) == 0 )
				{
					break;
				}
			}
			
			/* write the matching bytes from last iteration */
			fwrite(end, 1, pos, fp);
			pos = 0;
		}

		/* perform dot unescaping again */
		for ( i=0;i<=length-3;i++ )
		{
			if ( filebuffer[i] == '\n' && filebuffer[i+1] == '.' && filebuffer[i+2] == '.' )
			{
				memmove(filebuffer+i+1, filebuffer+i+2, length-i-2);
				length--;
			}
		}

		/* now check whether the end of the buffer matches the end sequence */
		pos = nntp_endCmp(filebuffer, length);
		stop = pos == 4;

		fwrite(filebuffer, 1, length-pos, fp);	

		length = sizeof(filebuffer);

		/* receive data from socket */
		if ( !stop && (result = socket_receive(state->socket, filebuffer, &length, 1)) != 0 )
		{
			fclose(fp);
			socket_freeBuffer(buffer);
			
			return result;
		}
	}	
	while ( !stop );

	fclose(fp);

	socket_freeBuffer(buffer);

	return NNTP_OK;
}

/**
	Returns how many characters of the passed buffer (at the end) match the article end sequence.
	@param buffer The data to check
	@param length The length of the data to check
	@returns int indicating the number of matched characters
*/
int nntp_endCmp(char* buffer, int length)
{
	int i =0;
	int state = -1;
	int n = length >= 4 ? length-4 : 0;
	char end[] = {'\n', '.', '\r', '\n'};

	for ( i=n;i<length;i++ )
	{
		if ( buffer[i] == end[state+1] )
			state++;
		else
			state = -1;
	}

	return state+1;
}

/**
	Selects the passed newsgroup
	@param state The NNTP session
	@param group Name of the group
	@returns int indicating success or not
*/
int nntp_selectGroup(void* nntpstate, const char* group)
{
	char sendbuffer[1024];
	nntpState* state = nntpstate;
	void* receivebuffer = socket_createBuffer();
	int size = 0;
	char* data = NULL;
	int ret = NNTP_ERROR;
	sprintf(sendbuffer, "GROUP %s\r\n", group);

	if ( socket_send(state->socket, sendbuffer, (int)strlen(sendbuffer)) )
	{
		if ( (data = socket_receiveLine(state->socket, receivebuffer, &size, 1)) != NULL )
		{
			if ( nntp_getStatusCode(data) == NNTP_GROUP_OK )
				ret = NNTP_OK;
		}
	}

	socket_freeBuffer(receivebuffer);

	return ret;
}

/**
	Sets user data that will be passed along with callbacks
	@param nntpstate The NNTP session
	@param userdata User data to be passed
	@returns void
*/
void nntp_setUserData(void* nntpstate, void* userdata)
{
	nntpState* state= nntpstate;

	state->userData = userdata;
}

/**
	Closes connections and frees resources
	@param nntpstate The NNTP session
	@returns void
*/
void nntp_close(void* nntpstate)
{
	nntpState* state= nntpstate;

	CLOSESOCKET(state->socket);

	free(state);
}
